package service.reportCenter.brewForecastReport;

import com.openbrew.openbrewrf.ACB;
import com.openbrew.openbrewrf.ACT;
import com.openbrew.openbrewrf.Administration;
import com.openbrew.openbrewrf.Allocation;
import com.openbrew.openbrewrf.Beer;
import com.openbrew.openbrewrf.FinishedGoodsInventory;
import com.openbrew.openbrewrf.PackageType;
import com.openbrew.openbrewrf.views.BrewView;
import com.openbrew.openbrewrf.views.FGIView;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import service.enumerations.MovementState;

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author dchapman
 */
@Stateless
@Path("com.openbrew.openbrewrf.brewforecastreport")
public class BrewForecastReportREST {
    @PersistenceContext(unitName = "com.openBrew_openBrewRF_war_1.0PU")
    private EntityManager em;
    
    //Array Lists used for report.
    private List<Beer> beers = new ArrayList<Beer>();
    private List<FinishedGoodsInventory> fgis = new ArrayList<FinishedGoodsInventory>();
    private List<BrewView> brews = new ArrayList<BrewView>();
    private List<Allocation> allocations = new ArrayList<Allocation>();
    private Administration admin;
    
    public BrewForecastReportREST() {
    }
    
    //Cache loading functions
    private void loadInProductionBeers(){
        beers.removeAll(beers);
        beers = getEntityManager().createNamedQuery("Beer.findAllInProduction", Beer.class).getResultList();
    }
    
    private void loadAllBeers(){
        beers.removeAll(beers);
        beers = getEntityManager().createNamedQuery("Beer.findAll", Beer.class).getResultList();
    }
    
    private void loadInProductionFGIs(){
        fgis.removeAll(fgis);
        fgis = getEntityManager().createNamedQuery("FinishedGoodsInventory.findAllInProduction"
                , FinishedGoodsInventory.class).getResultList();
    }
    
    private void loadAllFGIs(){
        fgis.removeAll(fgis);
        fgis = getEntityManager().createNamedQuery("FinishedGoodsInventory.findAll"
                , FinishedGoodsInventory.class).getResultList();
    }
    
    private void loadInProductionBrews(){
        brews.removeAll(brews);
        brews = getEntityManager().createNamedQuery("BrewView.findAllInProduction", BrewView.class).getResultList();
    }
    
    private void loadAllBrews(){
        brews.removeAll(brews);
        brews = getEntityManager().createNamedQuery("BrewView.findAll", BrewView.class).getResultList();
    }
    
    private void loadAllOpenAllocations(){
        allocations.removeAll(allocations);
        allocations = getEntityManager().createNamedQuery("Allocation.findOpenAllocations", Allocation.class).getResultList();
    }
    
    private void loadAdmin(){
        admin = getEntityManager().createNamedQuery("Administration.findAll", Administration.class).getResultList().get(0);
    }
    
    //Forecast Tools        
    private java.util.Date getBrewDate(final Beer beer, final java.util.Date dueDate){
        int days = beer.getBeerFermDays() + beer.getBeerSecondDays() + beer.getBeerCondDays();
        if(days == 0){
            //if the beer doesn't have days set, then we will set it to 15 days minimum lead time.
            days = 15;
        }
        days = days * -1;
        Calendar c = Calendar.getInstance();
        c.setTime(dueDate);
        c.add(Calendar.DATE, days);
        java.util.Date brewDate = new java.util.Date(c.getTime().getTime());
        
        return brewDate;
    }
    
    private double getAllocationBeerVolume(final Allocation al, final int beerID){
        double retVal = 0;
        if(al.getACTCollection()!=null){
            for(final ACT act : al.getACTCollection()){
                if(act.getACBCollection()!=null){
                    for(final ACB acb : act.getACBCollection()){
                        if(!acb.getACBFilled()){
                            final FGIView fgi = acb.getACBFGI();
                            if(fgi.getFGIBeerID().getbId() == beerID){
                                final PackageType pkg = fgi.getFGIPackageID();
                                if(pkg.isServingVessel()){
                                    retVal += (double)acb.getServingTankVolume();
                                } else {
                                    retVal += (double)pkg.getPackageTypeVol() * (double)fgi.getFGIPackageID().getNumItems() * (double)acb.getACBQty();
                                }
                            }
                        }
                    }
                }
            }
        }
        return retVal;
    }
    
    private double getCurrentFGIBeerVolume(final int beerID){
        double retVal = 0;
        for(final FinishedGoodsInventory fgi : fgis){
            if(fgi.getFGIBeerID().getbId() == beerID){
                final PackageType pkg = fgi.getFGIPackageID();
                retVal += (double)pkg.getPackageTypeVol() * (double)fgi.getFGIPackageID().getNumItems()  * (double)fgi.getFGIQtyOnHand();
            }
        }
        return retVal;
    }
    
    private double getCurrentFGIAllocationVolume(final int beerID){
        double retVal = 0;
        for(final FinishedGoodsInventory fgi : fgis){
            if(fgi.getFGIBeerID().getbId() == beerID){
                PackageType pkg = fgi.getFGIPackageID();
                retVal += (double)pkg.getPackageTypeVol() * (double)fgi.getFGIPackageID().getNumItems()  * (double)fgi.getFGIQtyAllocated();
            }
        }
        return retVal;
    }
    
    private double getBrewVolume(final BrewView brew){
        double retVal = 0;
        if((brew.getVolLeft() != 0)&&(brew.getbState() != MovementState.BREWING.getState())){
            //If brew is brewed, we know how much we have in tank, so we will return this.
            retVal = (double)brew.getVolLeft() * 0.9;
        } else {
            //Otherwise we need to determine how many batches are brewed and use that.
            retVal = (double)getBatchSize(brew) * (double)admin.getSystemSize() * 0.9;
        }
        return retVal;
    }
    
    public int getBatchSize(final BrewView brew){
        int numNotes=0;
        
        if(brew.getBrewNoteID()!=0){
            numNotes++;
        }
        if(brew.getBrewNoteID2()!=0){
            numNotes++;
        }
        if(brew.getBrewNoteID3()!=0){
            numNotes++;
        }
        if(brew.getBrewNoteID4()!=0){
            numNotes++;
        }
        if(brew.getBrewNoteID5()!=0){
            numNotes++;
        }
        
        return numNotes;
    }
    
    //Report Algorithm
    private List<BFReport> runBrewForecast(final Date today, final int daysOut){
        //Setup cache
        loadInProductionBeers();
        loadInProductionFGIs();
        loadInProductionBrews();
        loadAllOpenAllocations();
        loadAdmin();
        
        List<BFReport> forecastReport = new ArrayList<BFReport>();
        //Look through beers and determine needs.
        for(final Beer beer : beers){
            //Set up calendar for each and every beer that is currently in production.
            Calendar c = Calendar.getInstance();
            Calendar cStop = Calendar.getInstance();
            cStop.setTime(today);
            cStop.set(Calendar.HOUR_OF_DAY, 0);
            cStop.set(Calendar.MINUTE, 0);
            cStop.set(Calendar.SECOND, 0);
            cStop.set(Calendar.MILLISECOND, 0); 
            cStop.add(Calendar.DATE, daysOut); //90 default
            Date stopDate = new Date(cStop.getTime().getTime());
            c.setTime(today);
            c.set(Calendar.HOUR_OF_DAY, 0);
            c.set(Calendar.MINUTE, 0);
            c.set(Calendar.SECOND, 0);
            c.set(Calendar.MILLISECOND, 0); 
            c.add(Calendar.DATE, -30);
            Date date = new Date(c.getTime().getTime());

            //Get the initial volume in inventory for this beer.
            double volume = getCurrentFGIBeerVolume(beer.getBId());
            //Remove all allocated amounts from inventory (Currently on Pending SOs).
            volume = volume - getCurrentFGIAllocationVolume(beer.getBId());

            //Create a new recomended Schedule to add later.
            BFReport rec = new BFReport();
            rec.setBeerID(-1);
            
            double prevVolume = 0;

            //Now look through date by date for 60 days to determine when beer is out.
            //Add any brew of that beer that will be finished on said date,
            //Remove any brew of beer that will be sold on said date.
            while((date.before(stopDate))){
                for(final BrewView brew : brews){
                    //ensure that the brew isn't finished yet and that it is the correct beer.
                    if((brew.getbState() < 20)&&(brew.getbBeerID().getbId() == beer.getBId())){
                        if(brew.getGanttStop()!=null){
                            if(brew.getGanttStop().toString().equals(date.toString())){
                                //If it is on said date, add it to the brew
                                volume = volume + getBrewVolume(brew);
                            }
                        }
                    }
                }

                //Remove Allocation
                for(final Allocation a : allocations){
                    if(a.getAllocationDueDate().equals(date)){
                        //If the sale date is on this day we need to subtract it from the allocation table.
                        volume = volume - (getAllocationBeerVolume(a, beer.getBId())*1.20);
                    }
                }
                
                //If the volume at this point is less than zero and beer has not yet been set,
                //then we set it now and record this date as the earliest possible date.
                if((volume < 0) && (rec.getBeerID() == -1)){
                    rec.setBeerID(beer.getBId());
                    rec.setBeerName(beer.getBName());
                    rec.setDueDate(date);
                    rec.setBrewDate(getBrewDate(beer, date));
                    BFDetailReport bfdr = new BFDetailReport();
                    bfdr.setBrewDate(getBrewDate(beer, date));
                    bfdr.setDueDate(date);
                    bfdr.setVol((float)volume * -1);
                    List<BFDetailReport> details = new ArrayList<BFDetailReport>();
                    details.add(bfdr);
                    rec.setDetails(details);
                    prevVolume = volume;
                } else if ((volume<0) && (volume != prevVolume)){
                    BFDetailReport bfdr = new BFDetailReport();
                    bfdr.setBrewDate(getBrewDate(beer, date));
                    bfdr.setDueDate(date);
                    bfdr.setVol((float)volume * -1);
                    rec.getDetails().add(bfdr);
                    prevVolume = volume;
                } else if (volume >= 0){
                    prevVolume = 0;
                }
                
                //Increment Date by 1 day and look again.
                c.add(Calendar.DATE, 1);
                date = new Date(c.getTime().getTime());
            }
            if(rec.getBeerID() != -1){
                //if rec has been set we add it.
                if(volume < 0){
                    volume = volume * -1;
                }
                rec.setVol((float)volume);
                forecastReport.add(rec);
            }
        }
        return forecastReport;
    }
    
    @GET
    @Path("{date}/{days}")
    @Produces(MediaType.APPLICATION_XML)
    public List<BFReport> find(@PathParam("date") Date today
            , @PathParam("days") int daysToRun) {
        //Date today = new Date();
        
        Calendar c = Calendar.getInstance();      
        c.setTime(today);
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0); 
        
        today = new java.util.Date(c.getTime().getTime());
        
        return runBrewForecast(today, daysToRun);
    }
    
    protected EntityManager getEntityManager() {
        return em;
    }
}
